Skip to content

Read Slack through its MCP server with the user's own token - #58

Merged
haruotsu merged 1 commit into
mainfrom
add-slack-mcp-connection
Sep 7, 2026
Merged

Read Slack through its MCP server with the user's own token#58
haruotsu merged 1 commit into
mainfrom
add-slack-mcp-connection

Conversation

@haruotsu

@haruotsu haruotsu commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Why

Custom agents could not read Slack with the caller's own authorization. The slack catalog entry was retired because Slack's app OAuth pair (oauth/v2/authorize + oauth.v2.access) returns the user's token nested under authed_user.access_token, and Gemini Enterprise reads only the top-level access_token (RFC 6749), so the token never arrived.

Slack's MCP server (https://mcp.slack.com/mcp) advertises a separate authorization server whose pair, oauth/v2_user/authorize + oauth.v2.user.access, returns the user's token at the top level. That should let Gemini Enterprise's authorization reach it with nothing in between.

What

  • New catalog entry slack-mcp (src/gete/catalog/connections/slack-mcp.yaml), in the same shape as notion-mcp and freee-mcp. connections: [slack-mcp] plus an mcp: block pointing at https://mcp.slack.com/mcp is all an agent needs to declare.
  • The retired slack entry is removed. Nothing may declare it, and a live successor claiming the same token prefixes (xoxp-, xoxe.xoxp-) would otherwise be reported as clashing with a connection no token can reach. The tests that used it as the catalog's retired example now declare a retired connection of their own; the retired feature itself is unchanged.
  • README lists slack-mcp and no longer mentions the retired entry.
  • Conformance tests pin the entry's hosts, scope floor and menu, the scopes deliberately left out, the OAuth knobs left at their defaults, bot-token rejection, the setup text, and the absence of verified. A CLI test checks that gete connections slack-mcp prints the setup.

Design

  • hosts: [mcp.slack.com] only. The token is an ordinary Slack user token and the Web API would very likely take it, but the face of this connection is the MCP server's tools, which an mcp: block can hold to an allow list. Naming slack.com beside it would make every Web API method the scopes reach part of that face.
  • token_prefixes: [xoxp-, xoxe.xoxp-]. The tokens announce themselves, so the connection is not accepted by elimination and can sit beside notion-mcp or freee-mcp.
  • Default scopes are read-only (search, history, channel/member listing, profiles). search:read.files, files:read, chat:write, reactions:write, canvases and lists are on the optional_scopes menu. users:read.email, the conversation-creating *:write scopes, files:write and emoji:read are not offered at all. Slack fixes a user token's scopes at consent, so the floor is what reading Slack takes and later additions reach nobody already authorized until their authorization is reset.
  • scope_parameter, pkce and authorization_query are left unset. The user pair is reached by standard MCP clients as an RFC 8414 authorization server, which send a plain scope; S256 is supported, not required; a verbatim query would fix the scopes and the menu could not be offered beside it.
  • examples.rejects includes an xoxb- bot token. The connection is per user; refusing the shape keeps it so.
  • No verified:. No authorization has been taken through Gemini Enterprise yet.

Unconfirmed (stated as such in setup:)

  • Whether Gemini Enterprise sends the client secret as client_secret_post, the only method the metadata names.
  • Whether v2_user/authorize takes the scopes under scope; user_scope is the switch if it answers invalid_scope.
  • Whether Slack ignores the access_type / prompt parameters the authorization URL carries.
  • Whether Slack requires resource (RFC 8707), which Gemini Enterprise does not send.
  • Token expiry and refresh; the metadata offers refresh_token, and whether Gemini Enterprise refreshes is its own doing.

Facts read off the server

$ curl -s https://mcp.slack.com/.well-known/oauth-authorization-server
{"issuer":"https://mcp.slack.com",
 "authorization_endpoint":"https://slack.com/oauth/v2_user/authorize",
 "token_endpoint":"https://slack.com/api/oauth.v2.user.access",
 "grant_types_supported":["authorization_code","refresh_token","urn:ietf:params:oauth:grant-type:jwt-bearer"],
 "token_endpoint_auth_methods_supported":["client_secret_post"],
 "code_challenge_methods_supported":["S256"], ...}

$ curl -si -X POST https://mcp.slack.com/mcp -H 'Content-Type: application/json' \
    -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
HTTP/2 401
www-authenticate: Bearer resource_metadata="https://mcp.slack.com/.well-known/oauth-protected-resource"

Not in this PR

  • Creating or changing a Slack app; a person does that following gete connections slack-mcp.
  • verified:; it lands once an authorization has gone through Gemini Enterprise.
  • slack_post and runtime/slack_post.py are untouched.

Checks

  • uv run pytest (875 passed), uv run ruff check ., uv run ruff format --check ., uv run mypy
  • gete connections lists slack-mcp; gete connections slack-mcp prints the setup notes

The slack connection was retired because Slack's app OAuth pair returns
the user's token nested under authed_user, where Gemini Enterprise never
looks. Slack's MCP server advertises a separate user pair,
oauth/v2_user/authorize and oauth.v2.user.access, whose response carries
the user's token at the top level, so a custom agent can search and read
Slack as the caller with nothing in between.

slack-mcp reaches mcp.slack.com only: the token would very likely pass at
the Web API too, but the connection's face is the MCP server's tools,
which an mcp: block can hold to an allow list. The default scopes read;
files, writing, canvases and lists are on the menu; email addresses and
creating conversations are not offered at all.

The retired slack entry goes: nothing may declare it, and a live
successor claiming the same token prefixes would otherwise be reported
as clashing with a connection no token can reach. The tests that used it
as the retired example now declare one of their own.

Nothing here has been taken through Gemini Enterprise yet, so the entry
carries no verified date and its setup notes say what is unconfirmed.

🤖 Generated with Claude Code
@haruotsu
haruotsu merged commit 740dd89 into main Sep 7, 2026
3 checks passed
@haruotsu
haruotsu deleted the add-slack-mcp-connection branch September 7, 2026 10:59
@github-actions github-actions Bot mentioned this pull request Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant